<?php

namespace App\Handlers\Commands;

use App\Commands\CreateVulnerability as CreateVulnerabilityCommand;
use App\Entities\File;
use App\Entities\Vulnerability;
use App\Exceptions\ActionNotPermittedException;
use App\Exceptions\FileNotFoundException;
use App\Exceptions\InvalidInputException;
use App\Exceptions\UserNotFoundException;
use App\Exceptions\WorkspaceNotFoundException;
use App\Policies\ComponentPolicy;
use App\Repositories\AssetRepository;
use App\Repositories\FileRepository;
use App\Repositories\VulnerabilityRepository;
use App\Repositories\WorkspaceAppRepository;
use App\Services\ImageService;
use Doctrine\ORM\EntityManager;
use Exception;

class CreateVulnerability extends AbstractVulnerabilityHandler
{
    /** @var FileRepository */
    protected $fileRepository;

    /**
     * CreateVulnerability constructor.
     *
     * @param AssetRepository $assetRepository
     * @param VulnerabilityRepository $vulnerabilityRepository
     * @param WorkspaceAppRepository $workspaceAppRepository
     * @param EntityManager $em
     * @param ImageService $service
     * @param FileRepository $fileRepository
     */
    public function __construct(
        AssetRepository $assetRepository, VulnerabilityRepository $vulnerabilityRepository,
        WorkspaceAppRepository $workspaceAppRepository, EntityManager $em, ImageService $service,
        FileRepository $fileRepository
    )
    {
        parent::__construct($assetRepository, $vulnerabilityRepository, $workspaceAppRepository, $em, $service);
        $this->fileRepository = $fileRepository;
    }

    /**
     * Process the CreateAsset command.
     *
     * @param CreateVulnerabilityCommand $command
     * @return Vulnerability
     * @throws InvalidInputException
     * @throws UserNotFoundException
     * @throws WorkspaceNotFoundException
     * @throws Exception
     */
    public function handle(CreateVulnerabilityCommand $command)
    {
        // Get the authenticated User
        $requestingUser = $this->authenticate();

        /** @var Vulnerability $vulnerability */
        $fileId = $command->getId();
        $assetIds       = $command->getAssetIds();
        $vulnerability  = $command->getEntity();
        // Check that all the required fields were set on the command
        if (!isset($fileId, $assetIds, $vulnerability)) {
            throw new InvalidInputException("One or more required members are not set on the command");
        }

        /** @var File $file */
        $file = $this->fileRepository->find($fileId);
        // Make sure the File exists
        if (empty($file)) {
            throw new FileNotFoundException(
                "A File with the given ID does not exist."
            );
        }

        // Make sure we are creating a Vulnerability on a Ruggedy ScannerApp. Currently this is the only place where
        // creating custom Vulnerabilities is enabled
        if (!$file->getWorkspaceApp()->isRuggedyApp()) {
            throw new ActionNotPermittedException(
                "Cannot create Vulnerabilities on any WorkspaceApp other than the Ruggedy WorkspaceApp"
            );
        }

        // Make sure the User has permission to create a Vulnerability
        if ($requestingUser->cannot(ComponentPolicy::ACTION_UPDATE, $file)) {
            throw new ActionNotPermittedException(
                "The requesting User does not have permission to create new Vulnerabilities"
            );
        }

        // Associate the Vulnerability with the relevant File and persist the new Vulnerability
        // to the database via the File (cascade persist is enabled)
        $file->addVulnerability($vulnerability);
        $this->em->persist($file);

        $this->doVulnerabilityPostProcessing($vulnerability, $assetIds);

        // Save all changes to the DB and refresh the Vulnerability with the changes
        $this->em->flush();
        $this->em->refresh($vulnerability);

        return $vulnerability;
    }
}